﻿using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Windows.Data;
using System.ComponentModel;
using System.Windows.Input;
using Microsoft.Xna.Framework.Input.Touch;
using System.Threading;

namespace Code4WP7SDK.Controls
{
    public class Code4Calendar : Control
    {
        #region Fields
        public List<DateTime> DatesAssigned; //日期分配
        private const short factor = 1000;
        private const short RowCount = 7;       //行数
        private const short ColumnCount = 8;    //列数
        private int _year = DateTime.Today.Year;
        private int _month = DateTime.Today.Month;
        private bool _ignoreMonthChange;
        private Grid _itemsGrid;
        private bool _addedItems;
        private Code4CalendarItem _lastItem;
        #endregion

        #region Constructor
        public Code4Calendar()
        {
            DefaultStyleKey = typeof(Code4Calendar);
            DatesAssigned = new List<DateTime>();
			var binding = new Binding();
            Loaded += Code4Calendar_Loaded;
            SetBinding(PrivateDataContextProperty, binding);
            WireUpDataSource(DataContext, DataContext);
        }

        void Code4Calendar_Loaded(object sender, RoutedEventArgs e)
        {
            if (EnableGestures)
            {
                EnableGesturesSupport();
            }
        }
        #endregion

        #region Override
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            var prevBtn = GetTemplateChild("PrevMonthBtn") as Button;
            if (prevBtn != null)
            {
                prevBtn.Click += prevBtn_Click;
            }
            var nextBtn = GetTemplateChild("NextMonthBtn") as Button;
            if (nextBtn != null)
            {
                nextBtn.Click += nextBtn_Click;
            }
            _itemsGrid = GetTemplateChild("ItemsGrid") as Grid;
            BuildDates();
            SetYearMonthLabel();
        }
		
        void nextBtn_Click(object sender, RoutedEventArgs e)
        {
            IncrementMonth();
        }

        void prevBtn_Click(object sender, RoutedEventArgs e)
        {
            DecrementMonth();
        }
		
        private void BuildDates()
        {
            if (DatesSource != null)
            {
                DatesAssigned.Clear();
                DatesSource.ToList().ForEach(one => DatesAssigned.Add(one.CalendarItemDate));
            }
        }
        #endregion

        #region Dependency Properties
        public IEnumerable<ISupportedCalendarItem> DatesSource
        {
            get { return (IEnumerable<ISupportedCalendarItem>)GetValue(DatesSourceProperty); }
            set { SetValue(DatesSourceProperty, value); }
        }

        private static readonly DependencyProperty DatesSourceProperty =
            DependencyProperty.Register("DatesSource", typeof(IEnumerable<ISupportedCalendarItem>), typeof(Code4Calendar), new PropertyMetadata(null, OnDatesSourceChanged));

        private static readonly DependencyProperty DatePropertyNameForDatesSourceProperty =
            DependencyProperty.Register("DatePropertyNameForDatesSource", typeof(string), typeof(Code4Calendar), new PropertyMetadata(string.Empty, OnDatesSourceChanged));

        public static void OnDatesSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = (Code4Calendar)d;
            if (calendar != null)
            {
                calendar.BuildDates();
                calendar.BuildItems();
                if (e.OldValue is INotifyCollectionChanged)
                {
                    ((INotifyCollectionChanged)e.NewValue).CollectionChanged -= calendar.DataSourceChanged;
                }
                if (e.NewValue is INotifyCollectionChanged)
                {
                    ((INotifyCollectionChanged)e.NewValue).CollectionChanged += calendar.DataSourceChanged;
                }
            }
        }

        private void DataSourceChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            Refresh();
        }

        public object PrivateDataContext
        {
            get { return (object)GetValue(PrivateDataContextProperty); }
            set { SetValue(PrivateDataContextProperty, value); }
        }

        private static readonly DependencyProperty PrivateDataContextProperty =
            DependencyProperty.Register("PrivateDataContext", typeof(object), typeof(Code4Calendar), new PropertyMetadata(null,OnPrivateDataContextChanged));

        public static void OnPrivateDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = (Code4Calendar)d;
            if (calendar != null)
            {
                calendar.WireUpDataSource(e.NewValue, e.OldValue);
                calendar.Refresh();
            }
        }

        private void Refresh()
        {
            BuildDates();
            BuildItems();
        }
        /// <summary>
        /// 直接不明白
        /// </summary>
        /// <param name="newValue"></param>
        /// <param name="oldValue"></param>
        private void WireUpDataSource(object newValue, object oldValue)
        {
            if (newValue != null)
            {
                var source = newValue as INotifyPropertyChanged;
                if (source != null)
                {
                    source.PropertyChanged += SourcePropertyChanged;
                }
            }
            if (oldValue != null)
            {
                var source = newValue as INotifyPropertyChanged;
                if (source != null)
                {
                    source.PropertyChanged -= SourcePropertyChanged;
                }
            }
        }

        void SourcePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            var expression = GetBindingExpression(DatesSourceProperty);
            if (expression != null)
            {
                if (expression.ParentBinding.Path.Path.EndsWith(e.PropertyName))
                {
                    Refresh();
                }
            }
        }

        public IDateToBrushConverter ColorConverter
        {
            get { return (IDateToBrushConverter)GetValue(ColorConverterProperty); }
            set { SetValue(ColorConverterProperty, value); }
        }
        private static readonly DependencyProperty ColorConverterProperty =
            DependencyProperty.Register("ColorConverter", typeof(IDateToBrushConverter), typeof(Code4Calendar), new PropertyMetadata(null));


        public bool EnableGestures
        {
            get { return (bool)GetValue(EnableGesturesProperty); }
            set { SetValue(EnableGesturesProperty, value); }
        }

        private static readonly DependencyProperty EnableGesturesProperty =
            DependencyProperty.Register("EnableGestures", typeof(bool), typeof(Code4Calendar), new PropertyMetadata(false, OnEnableGesturesChanged));

        public static void OnEnableGesturesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = d as Code4Calendar;
            if (calendar.EnableGestures)
            {
                calendar.EnableGesturesSupport();
            }
            else
            {
                calendar.DisableGesturesSupport();
            }
        }

        public string YearMonthLabel
        {
            get { return (string)GetValue(YearMonthLabelProperty); }
            set { SetValue(YearMonthLabelProperty, value); }
        }

        private static readonly DependencyProperty YearMonthLabelProperty =
            DependencyProperty.Register("YearMonthLabel", typeof(string), typeof(Code4Calendar), new PropertyMetadata(""));


        public int SelectedMonth
        {
            get { return (int)GetValue(SelectedMonthProperty); }
            set { SetValue(SelectedMonthProperty, value); }
        }

        private static readonly DependencyProperty SelectedMonthProperty =
            DependencyProperty.Register("SelectedMonth", typeof(int), typeof(Code4Calendar), new PropertyMetadata(DateTime.Today.Month, OnSelectedYearMonthChanged));

        public int SelectedYear
        {
            get { return (int)GetValue(SelectedYearProperty); }
            set { SetValue(SelectedYearProperty, value); }
        }

        private static readonly DependencyProperty SelectedYearProperty =
            DependencyProperty.Register("SelectedYear", typeof(int), typeof(Code4Calendar), new PropertyMetadata(DateTime.Today.Year,OnSelectedYearMonthChanged));
        
        public static void OnSelectedYearMonthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = (Code4Calendar)d;
            if (calendar != null && (calendar._year != calendar.SelectedYear || calendar._month != calendar.SelectedMonth))
            {
                if (!calendar._ignoreMonthChange)
                {
                    calendar._year = calendar.SelectedYear;
                    calendar._month = calendar.SelectedMonth;
                    calendar.SetYearMonthLabel();
                }
            }
        }

        public bool ShowSelectedDate
        {
            get { return (bool)GetValue(ShowSelectedDateProperty); }
            set { SetValue(ShowSelectedDateProperty, value); }
        }

        private static readonly DependencyProperty ShowSelectedDateProperty =
            DependencyProperty.Register("ShowSelectedDate", typeof(bool), typeof(Code4Calendar), new PropertyMetadata(true));

        public DateTime SelectedDate
        {
            get { return (DateTime)GetValue(SelectedDateProperty); }
            set { SetValue(SelectedDateProperty, value); }
        }

        private static readonly DependencyProperty SelectedDateProperty =
            DependencyProperty.Register("SelectedDate", typeof(DateTime), typeof(Code4Calendar), new PropertyMetadata(DateTime.MinValue,OnSelectedDateChanged));

        public static void OnSelectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = (Code4Calendar)d;
            if (calendar != null)
            {
                calendar.OnSelectionChanged((DateTime)e.NewValue);
            }
        }

        public Style CalendarItemStyle
        {
            get { return (Style)GetValue(CalendarItemStyleProperty); }
            set { SetValue(CalendarItemStyleProperty, value); }
        }

        private static readonly DependencyProperty CalendarItemStyleProperty =
            DependencyProperty.Register("CalendarItemStyle", typeof(Style), typeof(Code4Calendar), new PropertyMetadata(null));

        public Style CalendarWeekItemStyle
        {
            get { return (Style)GetValue(CalendarWeekItemStyleProperty); }
            set { SetValue(CalendarWeekItemStyleProperty, value); }
        }

        private static readonly DependencyProperty CalendarWeekItemStyleProperty =
            DependencyProperty.Register("CalendarWeekItemStyle", typeof(Style), typeof(Code4Calendar), new PropertyMetadata(null));

        public WeekNumberDisplayOption WeekNumberDisplay
        {
            get { return (WeekNumberDisplayOption)GetValue(WeekNumberDisplayProperty); }
            set { SetValue(WeekNumberDisplayProperty, value); }
        }

        private static readonly DependencyProperty WeekNumberDisplayProperty =
            DependencyProperty.Register("WeekNumberDisplay", typeof(WeekNumberDisplayOption), typeof(Code4Calendar), new PropertyMetadata(WeekNumberDisplayOption.None,OnWeekNumberDisplayChanged));

        public static void OnWeekNumberDisplayChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var calendar = (Code4Calendar)d;
			if(calendar != null) 
			{
				calendar.BuildItems();
			}
        }

        public bool ShowNavigationButtons
        {
            get { return (bool)GetValue(ShowNavigationButtonsProperty); }
            set { SetValue(ShowNavigationButtonsProperty, value); }
        }

        private static readonly DependencyProperty ShowNavigationButtonsProperty =
            DependencyProperty.Register("ShowNavigationButtons", typeof(bool), typeof(Code4Calendar), new PropertyMetadata(true));
        #endregion

        #region Method
        private void EnableGesturesSupport()
        {
            DisableGesturesSupport();
            TouchPanel.EnabledGestures = GestureType.Flick; //这个地方应该分清方向
            ManipulationCompleted += Code4CalendarManipulationCompleted;
        }
        private void DisableGesturesSupport()
        {
            TouchPanel.EnabledGestures = GestureType.None;
            ManipulationCompleted -= Code4CalendarManipulationCompleted;
        }

        void Code4CalendarManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            while (TouchPanel.IsGestureAvailable)
            {
                var gesture = TouchPanel.ReadGesture();
                if (gesture.GestureType == GestureType.Flick)
                {
                    double horizontal = gesture.Delta.X / factor;
                    double vertical = gesture.Delta.Y / factor;
                    if (Math.Abs(horizontal) > Math.Abs(vertical))
                    {
                        //月份变化
                        if ((int)horizontal > 0)
                        {
                            IncrementMonth();
                            Debug.WriteLine("水平方向1");
                        }
                        else
                        {
                            DecrementMonth();
                            Debug.WriteLine("水平方向2");
                        }
                    }
                    else
                    {
                        if ((int)vertical > 0)
                        {
                            IncrementYear();
                        }
                        else
                        {
                            DecrementYear();
                        }
                    }
                }
            }
        }
        private void IncrementMonth()
        {
            if (_year != 2499 || _month != 12)
            {
                _month += 1;
                if (_month == 13)
                {
                    _month = 1;
                    _year += 1;
                }
                SetYearMonthLabel();
            }
        }
        /// <summary>
        /// 月份减小
        /// </summary>
        private void DecrementMonth()
        {
            if (_year != 1753 || _month != 1)
            {
                _month -= 1;
                if (_month == 0)
                {
                    _month = 12;
                    _year -= 1;
                }
                SetYearMonthLabel();
            }
        }

        private void IncrementYear()
        {
            if (_year != 2499)
            {
                _year += 1;
                SetYearMonthLabel();
            }
        }

        private void DecrementYear()
        {
            if (_year != 1753)
            {
                _year -= 1;
                SetYearMonthLabel();
            }
        }

        private void SetYearMonthLabel()
        {
            OnMonthChanging(_year, _month);
            YearMonthLabel = string.Concat(MonthHelper.GetChinaMonthName(_month),"", _year.ToString());
            _ignoreMonthChange = true;
            SelectedMonth = _month;
            SelectedYear = _year;
            _ignoreMonthChange = false;
            BuildItems();
            OnMonthChanged(_year, _month);
        }

        private void BuildItems()
        {
            if (_itemsGrid != null)
            {
                AddDefaultItems();
                var startOfMonth = new DateTime(_year, _month, 1);//本年本月的第一天
                DayOfWeek dayOfWeek = startOfMonth.DayOfWeek;
                var daysInMonth = (int)Math.Floor(startOfMonth.AddMonths(1).Subtract(startOfMonth).TotalDays);//???
                var addedDays = 0;
                int lastWeekNumber = 0;
                for (int rowCount = 1; rowCount <= RowCount; rowCount++)
                {
                    for (int columnCount = 1; columnCount < ColumnCount; columnCount++)
                    {
                        Code4CalendarItem item = (Code4CalendarItem)(from theChild in _itemsGrid.Children
                                    where theChild is Code4CalendarItem &&
                                    ((Code4CalendarItem)theChild).Tag.ToString() == string.Concat(rowCount.ToString(), ":", columnCount.ToString())
                                    select theChild).FirstOrDefault();
                        if (rowCount == 1 && columnCount < (int)dayOfWeek + 1)
                        {
                            item.Visibility = Visibility.Collapsed;
                        }
                        else if (addedDays < daysInMonth)
                        {
                            item.Visibility = Visibility.Visible;
                        }
                        else
                        {
                            item.Visibility = Visibility.Collapsed;
                        }
						
                        var weekItem = (Code4CalendarWeekItem)(from theChild in _itemsGrid.Children
                                                                   where theChild is Code4CalendarWeekItem &&
                                                                   ((Code4CalendarWeekItem)theChild).Tag.ToString() == string.Concat(rowCount.ToString(), ":0")
                                                                   select theChild).FirstOrDefault();
                        if (item.Visibility == Visibility.Visible)
                        {
                            item.ItemDate = startOfMonth.AddDays(addedDays);
                            if (SelectedDate == DateTime.MinValue && item.ItemDate == DateTime.Today)
                            {
                                SelectedDate = item.ItemDate;
                                if (ShowSelectedDate)
                                {
                                    item.IsSelected = true;
                                }
                                _lastItem = item;
                            }
                            else
                            {
                                if (item.ItemDate == SelectedDate)
                                {
                                    if (ShowSelectedDate)
                                    {
                                        item.IsSelected = true;
                                    }
                                    else
                                    {
                                        item.IsSelected = false;
                                    }
                                }
                            }
                            addedDays += 1;
                            item.DayNumber = addedDays;
                            item.SetBackColor();

                            
                            if (WeekNumberDisplay != WeekNumberDisplayOption.None)
                            {
                                int weekNumber;
                                if (WeekNumberDisplay == WeekNumberDisplayOption.WeekOfYear)
                                {
									var systemCalendar = Thread.CurrentThread.CurrentCulture.Calendar;
                                    weekNumber = systemCalendar.GetWeekOfYear(
									item.ItemDate, 
									Thread.CurrentThread.CurrentCulture.DateTimeFormat.CalendarWeekRule, 
									Thread.CurrentThread.CurrentCulture.DateTimeFormat.FirstDayOfWeek
									);
                                }
                                else
                                {
                                    weekNumber = rowCount;
                                }
                                weekItem.WeekNumber = weekNumber;
                                lastWeekNumber = weekNumber;
                                weekItem.Visibility = Visibility.Visible;
                            }
                        }
                        else
                        {
                            if (WeekNumberDisplay != WeekNumberDisplayOption.None && weekItem.WeekNumber != lastWeekNumber)
                            {
                                weekItem.Visibility = Visibility.Collapsed;
                            }
                        }
                    }
                }
            }
        }

        private void AddDefaultItems()
        {
            if (!_addedItems && _itemsGrid != null)
            {
                for (int rowCount = 1; rowCount <= RowCount; rowCount++)
                {
                    for (int columnCount = 1; columnCount < ColumnCount; columnCount++)
                    {
                        var item = new Code4CalendarItem(this);
                        item.SetValue(Grid.RowProperty, rowCount);
                        item.SetValue(Grid.ColumnProperty, columnCount);
                        item.Visibility = Visibility.Collapsed;
                        item.Tag = string.Concat(rowCount.ToString(),":",columnCount.ToString());
                        item.Click += ItemClick;
                        if (CalendarItemStyle != null)
                        {
                            item.Style = CalendarItemStyle;
                        }
                        _itemsGrid.Children.Add(item);
                    }
                    if (WeekNumberDisplay != WeekNumberDisplayOption.None)
                    {
                        const int columnCount = 0;
                        var item = new Code4CalendarWeekItem();
                        item.SetValue(Grid.RowProperty, rowCount);
                        item.SetValue(Grid.ColumnProperty, columnCount);
                        item.Visibility = Visibility.Collapsed;
                        item.Tag = string.Concat(rowCount.ToString(), ":", columnCount.ToString());
                        if (CalendarItemStyle != null)
                        {
                            item.Style = CalendarWeekItemStyle;
                        }
                        _itemsGrid.Children.Add(item);
                    }
                }
                _addedItems = true;
            }
        }

        void ItemClick(object sender, RoutedEventArgs e)
        {
            if (_lastItem != null)
            {
                _lastItem.IsSelected = false;
            }
            _lastItem = (Code4CalendarItem)sender;
            if (_lastItem != null)
            {
                if (ShowSelectedDate)
                {
                    _lastItem.IsSelected = true;
                }
                SelectedDate = _lastItem.ItemDate;
            }
        }

        private void OnMonthChanging(int year, int month)
        {
            if (MonthChanging != null)
            {
                MonthChanging(this, new MonthChangedEventArgs(year, month));
            }
        }

        private void OnMonthChanged(int year, int month)
        {
            if (MonthChanged != null)
            {
                MonthChanged(this, new MonthChangedEventArgs(year, month));
            }
        }

        private void OnSelectionChanged(DateTime dateTime)
        {
            if (this.SelectionChanged != null)
            {
                this.SelectionChanged(this, new SelectionChangedEventArgs(dateTime));
            }
        }
        
        #endregion

        #region Event
        public event EventHandler<MonthChangedEventArgs> MonthChanging;
        public event EventHandler<MonthChangedEventArgs> MonthChanged;
        public event EventHandler<SelectionChangedEventArgs> SelectionChanged; 
        #endregion
    }
}
